#include "../../externals/PixelToaster.h"
#include "../basics/basics.h"
#include "../math/trianglesurface.h"
#include "../math/functions.h"
#include "../math/meshobject.h"
#include "../transformation/camera.h"
#include "../engine/viewport.h"
#include "../engine/essential.h"
#include "../engine/import.h"
#include "../timer/chrtimer.h"
#include "../multicore/simplecontainer.h"
#include "../multicore/writetobuffer.h"
#include "../input/inputhandler.h"
#include "../sys/cls.h"
#include <Windows.h>
#include <stdio.h>

using namespace PixelToaster;

// ================================================================================================
// Refresh the cursor position
// ================================================================================================
void CursorRefresh(bool& cursorinit, POINT& center, POINT& startpt) {
    HWND hwnd;
    RECT rect;
    if(!cursorinit) {
        hwnd = GetActiveWindow();
        GetWindowRect(hwnd, &rect);
        ShowCursor(false);
        ClipCursor(&rect);
        center.x = (rect.left + rect.right) / 2;
        center.y = (rect.top + rect.bottom) / 2;
        SetCursorPos(center.x, center.y);
        GetCursorPos(&startpt);
        cursorinit = true;
    }
}

const float kFov = 45.0f;

// -- this includes the main thread
#define NUM_THREADS 4
uint kWidth = 640;
uint kHeight = 480;
bool kKill = false;

// ================================================================================================
// Entry point into the ray tracer application
// ================================================================================================
int main(int argv, char** argc) {
    // -- create the containers that hold threading information
    SimpleContainer containers[NUM_THREADS];

    // -- handles and ids to the threads
    #if NUM_THREADS != 1
        DWORD threadids[NUM_THREADS - 1];
        HANDLE hthreads[NUM_THREADS - 1];
    #endif

    // -- tickets for signaling to render and to signal done
    bool tickets[NUM_THREADS][4];

    // -- create the surfaces
    ImportTriangleMesh imesh(argc[1]);
    MeshObject mobject = imesh.GetMesh();

    // -- camera origin and viewport
    Vector4 cameracenter(0.0f, 0.0f, 2.0f);
    Vector4 cameraat(0.0f, 0.0f, -1.0f);
    Vector4 cameraup(0.0f, 1.0f, 0.0f);
    Camera camera(cameraat, cameraup, cameracenter);

    Viewport viewport("Ray Tracer", kWidth, kHeight, kFov, 6.0f);
    viewport.RefreshPixelBuffer();

    Rect2D viewrect;
    viewrect.topleft.x = 0;
    viewrect.topleft.y = 0;
    viewrect.bottomright.x = kWidth;
    viewrect.bottomright.y = kHeight;

    // -- divide the buffer and assign each thread
    DivideBuffer(containers, &viewport, viewrect, tickets);

    // -- transform the mesh to camera space
    MeshObject toview = mobject.TransformToCamSpace(camera);
    toview.OptimizeMesh();

    // -- initially transform mesh to camera space
    SetMeshes(containers, &toview);

    // -- set the tickets to a ready state
    ResetTicket(containers, NUM_THREADS);
    CHRTimer timer;

    CStopWatch stopwatch;
    uint framecount = 0;

    POINT startpt;
    POINT center;
    bool cursorinit = false;
    double summs = 0;
    double msticker = 0;
    while (viewport.GetDisplay().open()) {
        bool breaker = false;

        // -- start the threads
        #if NUM_THREADS != 1
            for(static uint i = 0; i < NUM_THREADS-1; ++i)  {
                hthreads[i] = CreateThread(NULL, 0, WriteToBuffer, &containers[i + 1],
                                           0, &threadids[i]);   
            }
        #endif

        // -- main thread will always be the first container
        MainThread(containers[0], viewport.GetDisplay());

        // -- check to see if all the threads have finished
        // -- frame production actually occurs here and not in the while loop
        if(CheckFinished(containers, NUM_THREADS)) {
            // -- present and refresh the buffers
            viewport.Present();

            // -- setup cursor for this window
            CursorRefresh(cursorinit, center, startpt);

            // -- reset refresh tickets
            ResetRefresh(containers, NUM_THREADS);

            // -- main thread refresh
            MainThreadRefresh(containers[0]);

            // -- wait for refresh to take place by the threads
            while(!CheckRefreshFinished(containers, NUM_THREADS)) {
                //...
            }

            // -- check for input
            breaker = InputHandler::HandleCameraInput(camera);
            InputHandler::HandleCameraMouseInput(camera, startpt, center);

            // -- reset the ticket activating the threads
            toview = mobject.TransformToCamSpace(camera);

            // -- calculate the accelerated mesh
            toview.OptimizeMesh();

            // -- transform mesh to camera space
            SetMeshes(containers, &toview);
 
            ResetTicket(containers, NUM_THREADS);

            stopwatch.StopClock();

            // -- every tenth frame print the ms
            if((framecount % 60) == 0) {
                ClearScreen();
                printf("%dms\n",int(stopwatch.GetDeltaMS()));
                msticker++;
                summs += stopwatch.GetDeltaMS();
            }
            ++framecount;
            stopwatch.StartClock();
        }
  
        if(breaker) {
            ResetRefresh(containers, NUM_THREADS);
            break;
        }
    }

    // -- signal the threads to die and cleanup
    #if NUM_THREADS != 1
        for(uint i = 1; i < NUM_THREADS; ++i) 
            *containers[i].GetKill() = true;
        WaitForMultipleObjects(NUM_THREADS-1, hthreads, TRUE, INFINITE);
        for(int i = 0; i < NUM_THREADS - 1; ++i)
            if(hthreads[i])
                CloseHandle(hthreads[i]);
    #endif

    cout << "AVG ms:" << summs/msticker << endl;

    return 0;
}
